﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace project3
{
    internal class Crawl
    {
        public event Action<Crawl> CrawlStopped;//相当于一个function的委托
        //这个function的入参必须有一个且为Crawl类型
        public event Action<Crawl,string,string> CrawlStarted;
        //创建一个等待下载的队列
        Queue<string> waitingfordownload = new Queue<string>();
        //下面的字典用来储存已经下载的网页
        public Dictionary<string,bool> haveDownloaded { get; } = new Dictionary<string,bool>();
        //get限定表明其只能取不能改

        //URL检测表达式，用于在HTML文本中查找URL
        public static readonly string UrlDetectRegex = @"(href|HREF)\s*=\s*[""'](?<url>[^""'#>]+)[""']";
        //URL解析表达式
        public static readonly string urlParseRegex = @"^(?<site>(?<protocal>https?)://(?<host>[\w\d.-]+)(:\d+)?($|/))(\w+/)*(?<file>[^#?]*)";
        
        public string HostFilter { get; set; }

        public string FileFilter { get; set; }
    
        public int MaxPage { get; set; }

        public string startURL { get; set; }

        public Encoding HtmlEncoding { get; set; }

        public Crawl()
        {
            MaxPage = 50;
            HtmlEncoding = Encoding.UTF8;
        }

        public void Start()
        {
            haveDownloaded.Clear();//一旦开始爬 就要把之前爬的东西给清空
            waitingfordownload.Clear();//不但要清空已经下载好的 还要把正在下载的也都清空掉
            waitingfordownload.Enqueue(startURL);//将startURL纳入到正在下载的队列中

            while(haveDownloaded.Count < MaxPage && waitingfordownload.Count> 0)//意思就是已经下载的小于最大页数且还有等待下载的
            {
                string tempURL = waitingfordownload.Dequeue();//将将要进行下载操作的这个url文本出列
                try
                {
                    string temphtmlstr = DownLoad(tempURL);
                    haveDownloaded[tempURL] = true;
                    CrawlStarted(this, tempURL, "success");
                    Parse(temphtmlstr, tempURL);
                }
                catch (Exception ex)
                {
                    haveDownloaded[tempURL] = false;
                    CrawlStarted(this,tempURL, ex.Message);
                }
            }
        }

        private string DownLoad(string url)
        {
            WebClient webClient = new WebClient();
            webClient.Encoding = Encoding.UTF8;
            string downloadhtml = webClient.DownloadString(url);
            string fileName = haveDownloaded.Count.ToString();
            File.WriteAllText(fileName, downloadhtml,Encoding.UTF8);//这句话没搞懂是啥意思
            return downloadhtml;
        }

        private void Parse(string htmltext,string Pageurl)//Pageurl指的就是这个这个网页本身的地址
        {
            var matches = new Regex(UrlDetectRegex).Matches(htmltext);//这个检测的和那个底下那个不一样是两种筛选
            foreach(Match match in matches)
            {
                string linkUrl = match.Groups["url"].Value;
                if (linkUrl == null || linkUrl == "" || linkUrl.StartsWith("javascript:")) continue;
                ///在所有匹配的url的项中，如果遇到这三种情况 那么直接跳过不写
                linkUrl = FixUrl(linkUrl, Pageurl);

                Match hostandFile = Regex.Match(linkUrl, urlParseRegex);
                waitingfordownload.Enqueue(linkUrl);//为什么能源源不断地爬取 其原因就在这个位置
                
            }
        }
        
        static private string FixUrl(string url, string pageUrl)//将非完整路径转化为完整路径 这个路径和我们输入的网址没毛关系
            //它指的是我们在网页中爬取到了url的内容 把这些个内容的相对地址全部转换为绝对地址
        {
            if (url.Contains("://"))
            {
                return url;//完整路径自然就直接返回啦
            }
            if (url.StartsWith("//"))
            {
                System.Text.RegularExpressions.Match urlMatch = Regex.Match(pageUrl, urlParseRegex);
                string protocal = urlMatch.Groups["protocal"].Value;
                return protocal + ":" + url;//那么就返回原表达式中protocal的值加上爬取到的url 实际上观察正则 就是https
            }
            if (url.StartsWith("/"))
            {
                Match urlMatch = Regex.Match(pageUrl,urlParseRegex);//还是首先先进行匹配
                string site = urlMatch.Groups["site"].Value;
                return site.EndsWith("/") ? site + url.Substring(1) : site + url;
                //如果url以/开头 那么就将url去掉第一个斜杠 并将其与site拼接
            }

            if (url.StartsWith("../"))
            {
                url = url.Substring(3);//首先url头部的三个字符给去掉
                int idx = pageUrl.LastIndexOf('/');
                return FixUrl(url,pageUrl.Substring(0,idx));//相当于把pageURL剥了两次壳
            }

            if (url.StartsWith("./"))
            {
                return FixUrl(url.Substring(2), pageUrl);//如果是这种情况 就直接剥一层壳就可以了
            }

            int end = pageUrl.LastIndexOf("/");
            return pageUrl.Substring(0, end) + "/" + url;
        }
    }
}
